using System;
using System.Collections.Generic;
using System.Linq;
using Mono.Cecil;

namespace LoggingMagic.AssemlyOperations
{
    internal class AttributesFinder
    {
        private readonly IAssemblyResolver _resolver;
        private readonly ModuleDefinition _module;

        public HashSet<string> Magic { get; private set; }
        public HashSet<string> NoMagic { get; private set; }
        readonly HashSet<string> _processed = new HashSet<string>();

        static readonly string[] _ignores = new[] { "mscorlib,", "System.", "Microsoft.", "DevExpress." };

        public AttributesFinder(IAssemblyResolver resolver, ModuleDefinition module)
        {
            _resolver = resolver;
            _module = module;

            Magic = new HashSet<string>();
            NoMagic = new HashSet<string>();
        }

        private string _applyAttributeName;
        private string _disableAttributeName;

        /// <summary>
        /// Finds attributes with given names in current assembly and all the referenced assemblies
        /// Fills Magic and NoMagic HashSets with attribute full names
        /// </summary>
        public bool FindAttributes(string attributeName, string disableAttributeName)
        {
            _applyAttributeName = attributeName;
            _disableAttributeName = disableAttributeName;

            FindMagic(_module);

            return Magic.Count > 0;
        }


        /// <summary>
        /// Small filter method to discard not interesting assemblies. By default System, MS & DevEx.
        /// </summary>
        private bool IsUserAssembly(AssemblyNameReference assembly)
        {
            var name = assembly.FullName;

            return !_ignores.Any(i => name.StartsWith(i, StringComparison.InvariantCultureIgnoreCase));
        }

        private void FindMagic(ModuleDefinition module)
        {
            if (_processed.Contains(module.Assembly.FullName))
                return;

            _processed.Add(module.Assembly.FullName);

            FindMagicInModule(module);

            foreach (var reference in module.AssemblyReferences)
                if (IsUserAssembly(reference))
                    try
                    {
                        var assembly = _resolver.Resolve(reference);

                        if (assembly != null)
                            FindMagic(assembly.MainModule);
                    }
                    catch (Exception)
                    {
                        // TODO: Log exception
                    }
        }

        void FindMagicInModule(ModuleDefinition module)
        {
            Magic.UnionWith(AttributesWithName(module, _applyAttributeName));
            NoMagic.UnionWith(AttributesWithName(module, _disableAttributeName));
        }

        static IEnumerable<string> AttributesWithName(ModuleDefinition module, string name)
        {
            return from t in module.Types
                   where t.IsClass && !t.IsAbstract && t.Name == name && t.BaseType != null && t.BaseType.FullName == "System.Attribute"
                   select t.FullName;
        }
    }
}